package br.edu.unifei.banded;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.banded.mn.CoarseHelper;
import br.edu.unifei.mestrado.commons.graph.GraphWrapper;
import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.graph.RelationshipWrapper;
import br.edu.unifei.mestrado.commons.partition.AbstractPartition;
import br.edu.unifei.mestrado.commons.partition.AbstractPartitionIndex;
import br.edu.unifei.mestrado.commons.partition.BestPartition;
import br.edu.unifei.mestrado.commons.partition.SimplePartition;
import br.edu.unifei.mestrado.kl.util.Partition;
import br.edu.unifei.mestrado.mn.TwoWayMultinivel;

public abstract class Banded2WayDiffusion extends TwoWayMultinivel {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private static final int numberOfPasses = 10;

	private SimplePartition partition;

	private long seed1;

	private long seed2;

	public Banded2WayDiffusion(long seed1, long seed2) {
		this.seed1 = seed1;
		this.seed2 = seed2;
	}

	public abstract AbstractPartitionIndex getPartitionIndex();

	@Override
	protected BestPartition executePartition(GraphWrapper grafo) {
		BestPartition bestPartition = execute(numberOfPasses);
		return bestPartition;
	}

	@Override
	protected BestPartition refineGraph(GraphWrapper graph, int level) {
		GraphWrapper bandedGraph = createNewGraph(AbstractPartition.NO_LEVEL);
		generateBand(level, AbstractPartition.TWO_WAY, graph, bandedGraph);

		// UTIL: aqui somente cria o bestPartition a partir do grafo
		long delta = System.currentTimeMillis();
		long deltaGenPart = System.currentTimeMillis();
		Partition basePartition = new Partition(level, getPartitionIndex());
		basePartition.generateBoundaryPartition(getGrafo().getAllEdges());

		// TODO: não precisa mais, pq o código da frontier foi pro Partition
		// //UTIL: precisa incluir os nós iniciais na fronteira também
		// frontier.includeInitialFrontier(basePartition.getAllNodes());

		deltaGenPart = System.currentTimeMillis() - delta;
		logger.warn("Tempo gasto para BandDifusion gerar boundary partition: " + deltaGenPart + " ms");

		// UTIL: não pode ser fixed, pq já está particionado vindo do KL
		// basePartition.initialFixedPartition(grafoM_1.getAllNodes());

		int bestCut = basePartition.getCutWeight();
		logger.warn("Corte inicial BKL: " + bestCut);

		BestPartition bestPartition = basePartition.createBestPartition();

		return bestPartition;
	}

	public BestPartition execute(int numberOfPasses) {
		partition = new SimplePartition(GraphWrapper.FIRST_lEVEL, AbstractPartition.TWO_WAY, getPartitionIndex());
		execute2WayDiffusion(numberOfPasses);
		return partition.createBestPartition();
	}

	private void execute2WayDiffusion(int numberOfPasses) {

		Map<Long, Integer> oArray = getOArray();
		int N = getGraphWeight();
		int initialLiquid = N / 2;
		int media_grau_vertices = N / getGrafo().getSizeNodes();
		if (media_grau_vertices < 2) {
			initialLiquid = N;
		}

		Map<Long, Integer> newArray = new HashMap<Long, Integer>();
		Map<Long, Integer> oldArray = new HashMap<Long, Integer>();
		for (int pass = 0; pass < numberOfPasses; pass++) {
			newArray = new HashMap<Long, Integer>(); // resetArray(newArray);

			addFToMap(oldArray, getGrafo().getNode(seed1), seed1, initialLiquid);
			addFToMap(oldArray, getGrafo().getNode(seed2), seed2, -initialLiquid);

			List<NodeWrapper> novosNodes = new ArrayList<NodeWrapper>();
			for (long nodeId : oldArray.keySet()) {
				NodeWrapper node = getGrafo().getNode(nodeId);
				Integer c = oldArray.get(nodeId);

				int weight = node.getWeight();
				if (Math.abs(c) > weight) {
					int signal = c < 0 ? -1 : 1;//pega o sinal de c
					c = c - weight * signal;
					
					//UTIL: salva o valor de c no oldArray!!! ===>>> ISSO NÃO TEM NO ALGORITMO DO ARTIGO
					oldArray.put(nodeId, c);
					
					int o = oArray.get(node.getId());
					for (RelationshipWrapper edge : node.getRelationships()) {
						NodeWrapper otherNode = edge.getOtherNode(node);
						long otherNodeId = otherNode.getId();
						int f = c * edge.getWeight() / o;

						// adiciona o valor de f na posicao de otherNodeId
						// newArray[otherNodeId] = newArray[otherNodeId] + f;
						boolean changeOfPartition = addFToMap(newArray, otherNode, otherNodeId, f);
						if (changeOfPartition) {
							updateEdgeCut(c, f, edge);
							novosNodes.add(otherNode);
							repaint();
						}

					}// end for
				}// end if
			}// end for

			logger.info("Novos nodes afetados pelo passo {}: {}", pass, novosNodes);

			updateView(getGrafo(), -1, "-1", -1);
			Map<Long, Integer> tmpArray = newArray;
			newArray = oldArray;
			oldArray = tmpArray;
		}
	}

	/**
	 * 
	 * @param newArray
	 * @param otherNodeId
	 * @param f
	 * @return true se o valor mudou de sinal, isto é, se o node mudou de partição
	 */
	private boolean addFToMap(Map<Long, Integer> newArray, NodeWrapper otherNode, long otherNodeId, int f) {
		boolean changeOfPartition = false;
		Integer value = newArray.get(otherNodeId);
		if (value == null) {
			value = f;
			changeOfPartition = true;
		} else {
			int valueAnt = value;
			value += f;

			// codigo para verificar se o valor mudou de sinal, ou seja, mudou de partição
			if (valueAnt >= 0) {
				if (value < 0) {
					changeOfPartition = true;
				}
			} else {
				if (value >= 0) {
					changeOfPartition = true;
				}
			}
		}
		newArray.put(otherNodeId, value);
		otherNode.setD(value);
		
		if(changeOfPartition) {
			updateNodePartition(otherNode, f);
		}
		return changeOfPartition;
	}

	public SimplePartition generateBand(int level, int k, GraphWrapper oldGraph,
			GraphWrapper newGraph) {
		SimplePartition partition = new SimplePartition(level, k, getPartitionIndex());
		CoarseHelper coarser = new CoarseHelper(partition);
		coarser.coarseOneLevel(level, oldGraph, newGraph);
		return partition;
	}

	private int getGraphWeight() {
		int N = 0;
		for (NodeWrapper node : getGrafo().getAllNodes()) {
			N += node.getWeight();
		}
		return N;
	}

	private Map<Long, Integer> getOArray() {
		Map<Long, Integer> oArray = new HashMap<Long, Integer>();
		for (NodeWrapper node : getGrafo().getAllNodes()) {
			int o = 0;

			for (RelationshipWrapper edge : node.getRelationships()) {
				o += edge.getWeight();
			}
			oArray.put(node.getId(), o);
		}
		return oArray;
	}

	private int getSetIdOf(int value) {
		int idSet;
		if (value >= 0) {
			idSet = AbstractPartition.PART_1;
		} else {
			idSet = AbstractPartition.PART_2;
		}
		return idSet - 1;
	}

	private void updateNodePartition(NodeWrapper node, int nodeValue) {
		int setId = getSetIdOf(nodeValue);
		node.setPartition(setId);
		partition.addNodeToSet(setId, node);
	}

	private void updateEdgeCut(Integer valueNodeId, int valueOtherNodeId, RelationshipWrapper edge) {
		if (valueNodeId == null) {// se o nodeId ainda nao estiver no map, entao a aresta deve ser adicionada
			partition.addEdgeToCut(edge);
			return;
		} else {

		}

		if (valueNodeId > 0) {
			if (valueOtherNodeId < 0) {
				partition.addEdgeToCut(edge);
			} else if (valueOtherNodeId > 0) {
				partition.removeEdgeFromCut(edge);
			}

		} else if (valueNodeId < 0) {
			if (valueOtherNodeId > 0) {
				partition.addEdgeToCut(edge);
			} else if (valueOtherNodeId < 0) {
				partition.removeEdgeFromCut(edge);
			}
		}
	}
}
